Published in:2024-10-24 |

面向对象三大特性

一、简介

1、封装
1
封装就是隐藏对象的属性和实现细节,仅对外公开接口(公有的方法),控制在程序中的属性的读和修改的访问级别,将数据和行为进行有机的结合,形成一个整体。这个整体就是“类”,其中数据和方法都是类中的成员。
2、继承
1
继承:子类直接使用父类定义好的属性和方法,从而实现代码的复用。code reuse
3、多态
1
向不同的对象发送同一条信息,不同的对象在接受到信息后,会做出不同的反应.

二、封装

  • 将属性和方法进行有机结合,形成一个整体
  • 将不想暴露给外界的成员私有化,外界只能通过公有的接口访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Person:
def __init__(self, name, age):
self.__name = name
self.__age = age

def get_age(self):
return self.__age

def set_age(self,age):
# 在公有的接口中可以对数据加以处理
if age < 0 or age > 150:
print("年龄不合法")
else:
self.__age = age

def __str__(self):
return f"姓名:{self.__name},年龄:{self.__age}"


p = Person('Tom',18)
print(p) # 直接打印对象 获取到的是地址

# p.age = 20 # 当age为私有就不可以在外界随意修改访问
# print(p)
#
# p.age = 200
# print(p)

p.set_age(200)
print(p)
  • property函数
1
2
3
4
5
6
age = property(get_age, set_age, delete_age)

p.age = 20 # 自动调用setter -- set_age()
print(p.age) # 自动调用getter -- get_age()

del p.age # 自动调用delete -- delete_age()
1
2
3
4
5
print(p.__dict__) # {'_Person__name': 'Tom', '_Person__age': 18}
del p.age
print(p.__dict__) # {'_Person__name': 'Tom'}

print(Person.__dict__)

三、继承

1
2
3
4
5
6
7
8
9
10
1. 子类直接使用父类定义好的属性和方法,从而实现代码复用.

2. 在Python中如果程序需要,可以让一个类去继承另一个类,被继承的类称为父类或者超类,也可以称为基类,继承的类称为子类或派生类.

3. 如果创建类时,没有手动去设置父类,默认继承自object类,顶层类
(在Python中,万物皆对象)

4. 继承是is-a的关系 一个学生是一个人 一个毕业生是一个学生

5. 当子类继承父类后,会拥有父类所有的属性和方法
1、简单的继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person:
def __init__(self,name,age):
self.name = name
self.age = age

def __str__(self):
return f"姓名:{self.name},年龄:{self.age}"

class Student(Person):
pass

# 当子类没有实现__init__方法时,默认会调用父类的__init__
s = Student() # __init__() missing 2 required positional arguments: 'name' and 'age'


s = Student('Tom',18)
print(s) # 姓名:Tom,年龄:18
2、私有成员

子类会继承父类的私有成员,但是子类并不能直接去访问继承自父类的私有成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person:
def __init__(self, name, age):
self.__name = name
self.__age = age

def __str__(self):
return f"姓名:{self.__name},年龄:{self.__age}"


class Student(Person):
def output(self):
return self.__name # 子类不能直接访问父类的私有成员


s = Student('Tom',18)
s.output(

如何解决:

  • 子类通过继承自父类的公有的方法来访问继承自父类的私有成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person:
def __init__(self, name, age):
self.__name = name
self.__age = age

def get_name(self): # 声明公有的get方法
return self.__name

def __str__(self):
return f"姓名:{self.__name},年龄:{self.__age}"


class Student(Person):
def output(self):
# print(self.__name)
print(self.get_name()) # 通过父类的公有方法来访问私有成员


s = Student('Tom',18)
s.output()
  • 将父类的私有成员改为受保护的成员 (有问题)
    • 声明属性时,使用一个_属性名
    • 对于外界依然是“私有”的,可以访问不报错,会有警告
    • 子类可以直接去访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person:
def __init__(self, name, age):
self._name = name
self._age = age

def get_name(self):
return self._name

def __str__(self):
return f"姓名:{self._name},年龄:{self._age}"


class Student(Person):
def output(self):
print(self._name)


p = Person('Jack', 20)
print(p._name) # 这里不报错 但是有警告

s = Student('Tom', 18)
s.output()

print(s._name)
3、方法重写

子类定义了与父类方法名一致的访问,称为方法的重写(覆盖override)

1
2
3
4
5
6
7
8
9
class Father:
def who_am_i(self):
print('我是爸爸')

class Son(Father):
pass

son = Son()
son.who_am_i() # 我是爸爸 -- 不合理

所以,如果子类有需要,需要去重写父类的同名方法:

1
2
3
4
5
6
7
8
9
10
11
12
class Father:
def who_am_i(self):
print('我是爸爸')


class Son(Father):
def who_am_i(self):
print('我是儿子')


son = Son()
son.who_am_i() # 我是儿子
4、子类的__init__方法

当子类没有重写父类的__init__,在实例化子类对象时会默认调用父类的__init__方法

如果子类重写了__init__方法,此时再实例化子类对象时,就不再隐式地去调用父类的__init__方法了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person:
def __init__(self, name, age):
self.name = name
self.age = age


def __str__(self):
return f"姓名:{self.name},年龄:{self.age}"


class Student(Person):
def __init__(self,id):
self.id = id

# 子类定义了init后 不再隐式调用父类init
s = Student(1)
print(s) # AttributeError: 'Student' object has no attribute 'name'

此时再去访问name时,会提示没有name属性,原因于父类的init未被调用。所以如果子类重写了init方法,为了能够使用或扩展父类的行为,最好显式地调用父类的init方法

1
2
3
4
5
6
7
8
9
10
11
# 1. 使用 类名.__init(self,xxx,xx) 显式在子类的init中调用父类的init
class Student(Person):
def __init__(self,id,name,age):
self.id = id
# 下面的self是学生对象s
Person.__init__(self,name,age)


s = Student(1,'Tom',18)
print(s.__dict__) # {'id': 1, 'name': 'Tom', 'age': 18}
print(s) # 姓名:Tom,年龄:18
1
2
3
4
5
# 2. 使用 super().__init__(name,age)  # super超级 不需要传self
class Student(Person):
def __init__(self,id,name,age):
self.id = id
super().__init__(name,age) # super超级
5、多继承

Python是支持多继承,一个子类可以有多个父类

1
2
3
4
5
6
7
8
9
class A:
a = 100
class B:
b = 200
class C(A,B):
c = 300

obj = C()
print(obj.a)
6、菱形继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 菱形继承:1. 结构混乱 2.资源浪费
class A:
def __init__(self):
print('A init')


class B(A):
def __init__(self):
print('B init')
A.__init__(self)


class C(A):
def __init__(self):
print('C init')
A.__init__(self)


class D(B,C):
def __init__(self):
print('D init')
B.__init__(self)
C.__init__(self)

d = D()

结果:

1
2
3
4
5
6
D init
B init
A init
C init
A init
# 从结果得知,A的init被调用了两次,造成资源的浪费

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 在子类中使用super().__init()
class A:
def __init__(self):
print('A init')


class B(A):
def __init__(self):
print('B init')
super().__init__()


class C(A):
def __init__(self):
print('C init')
super().__init__()


class D(B,C):
def __init__(self):
print('D init')
super().__init__()
# B.__init__(self)
# C.__init__(self)

d = D()

在Python中,已经定义好了一个魔法方法来帮助我们去查看类的继承顺序,super()按照MRO的顺序来进行继承。

1
2
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
1
2
3
4
5
# 补充super():
- super(type,obj) 和 super() 等价
- type:类型 默认是当前类 也可以是MRO链式结构中的父类
- obj :对象 默认是当前对象
通过super()底层是使用MRO找到当前类的继承链,从而通过继承链来调用其父类,避免顶层类被多次调用的问题

四、多态

1
2
1. 多态:多种形态
2. 多态性:向不同的对象发送相同的指令,不同的对象会做出不同的响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Animal:
def speak(self):
pass

class Dog(Animal):
def speak(self):
print('汪汪汪')

class Cat(Animal):
def speak(self):
print('喵喵喵')

d = Dog()
c = Cat()
d.speak() # 发送的指令是一样,做出的响应是不一样的
c.speak()

def animal_speak(animal):
animal.speak()

animal_speak(d)
animal_speak(c)
1
2
3
多态性好处:
1. 增加程序的灵活性,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用 animal.speak(ojb),但是不同的对象做出的响应是不一样的
2. 增加了程序的可扩展性

五、内置函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 1. issubclass(cls,class) 判断一个类是否是另一个类的子类 
class A:
pass
class B(A):
pass
print(issubclass(A,B)) # False
print(issubclass(B,A)) # True

# 2. isinstance(obj,class) 判断某个对象是否为类/子类的实例
a = A()
print(isinstance(a,A)) # True
print(isinstance(B(),A)) # True
print(isinstance(B(),object)) # True

# 3. hasattr() 判断对象是否有某个属性
class A:
def __init__(self):
self.a = 100
self.b = 200

obj = A()
print(hasattr(obj,'a')) # True

# 4. getattr(obj,name[,default]) # 获取对象的属性 如果不存在会报错 如果设置了默认值,不存在时返回默认值
print(getattr(obj,'a')) # 100 和 obj.a是等价的
print(getattr(obj,'c')) # 报错
print(getattr(obj,'c',300)) # 返回300

# 5. setattr(obj,name,value) # 设置对象属性值,不存在时添加一个新的
setattr(obj,'c',500)
print(obj.c)

# 6. delattr(obj,name) # 删除对象的某个属性
delattr(obj,'a')
print(obj.__dict__)
Prev:
Next: